/*
 FUSE: Filesystem in Userspace
 Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>

 This program can be distributed under the terms of the GNU GPL.
 See the file COPYING.

 gcc -Wall hello.c `pkg-config fuse --cflags --libs` -o hello
 */

#define FUSE_USE_VERSION 26

#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <jni.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdlib.h>

static JNIEnv *mainEnv;
static JavaVM *jvm;
static jclass cls;
static jmethodID readdir;
static jmethodID isFile;
static jmethodID getBlockNo;
static jmethodID readFileContent;
static jmethodID getFileLength;

/*
 struct Userdata {
 JNIEnv *env;
 JavaVM *jvm;

 jclass cls;
 jmethodID readdir;
 jmethodID isFile;
 jmethodID getBlockNo;
 jmethodID readFileContent;
 jmethodID getFileLength;
 };
 */

static JNIEnv* create_vm() {
	JavaVMInitArgs vm_args;
	JavaVMOption options;
	options.optionString = "-Djava.class.path=../target/PFSBuilder-20140619.jar";
	vm_args.version = JNI_VERSION_1_6;
	vm_args.nOptions = 1;
	vm_args.options = &options;
	vm_args.ignoreUnrecognized = 0;
	printf("jvm=%x\n",jvm);
	jint res = JNI_CreateJavaVM(&jvm, (void**) &mainEnv, &vm_args);
	if (res < 0) {
		printf("Can't create Java VM\n");
		return NULL;
	}
	printf("jvm=%x\n",jvm);
	return mainEnv;
}

static void free_JVM(JNIEnv *env) {
	if (jvm != NULL) {
		(*jvm)->DestroyJavaVM(jvm);
		jvm = NULL;
		env = NULL;
	}
}

static JNIEnv *get_env() {
	JNIEnv * env;
	JavaVMAttachArgs args;

	args.version = JNI_VERSION_1_4;
	args.name = NULL;
	//args.group = threadGroup;

	// a GCJ 4.0 bug workarround (supplied by Alexander Boström <abo@stacken.kth.se>)
	if ((*jvm)->GetEnv(jvm, (void**) &env, args.version) == JNI_OK)
		return env;

	printf("will attach thread\n");

	// attach thread as daemon thread so that JVM can exit after unmounting the fuseFS
	(*jvm)->AttachCurrentThreadAsDaemon(jvm, (void**) &env, (void*) &args);

	printf("did attach thread to env: %p\n", env);

	return env;
}

static void release_env(JNIEnv *env) {
	if (env == mainEnv) {
		printf("will NOT detach main thread from env: %p\n", env);
	} else {
		printf("will NOT detach thread from env: %p\n", env);
	}
}

static int peter_open(const char *path, struct fuse_file_info *fi) {
	JNIEnv *env = get_env();

	printf("peter_open = %s\n", path);
//	if (strcmp(path, peter_path) != 0)
//		return -ENOENT;

//	if ((fi->flags & 3) != O_RDONLY)
//		return -EACCES;

	jstring pathPara = (*env)->NewStringUTF(env, path);
	long blockNo = (*env)->CallStaticLongMethod(env, cls, getBlockNo, pathPara);
	release_env(env);

	if (blockNo == -1) {
		return -ENOENT;
	}
	printf("blockNo = %ld\n", blockNo);

	return 0;
}
static int peter_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) {
	JNIEnv *env = get_env();

	printf("peter_readdir = %s\n", path);

	jstring pathPara = (*env)->NewStringUTF(env, path);
	long blockNo = (*env)->CallStaticLongMethod(env, cls, getBlockNo, pathPara);

	if (blockNo == -1) {
		release_env(env);
		return -ENOENT;
	}

	filler(buf, ".", NULL, 0);
	filler(buf, "..", NULL, 0);
	//filler(buf, peter_path + 1, NULL, 0);

//

	jstring paraReadDir = (*env)->NewStringUTF(env, path);
	jobjectArray returnArray = (jstring)(*env)->CallStaticObjectMethod(env, cls, readdir, paraReadDir);
	int len = (*env)->GetArrayLength(env, returnArray);

	int x;
	printf("\tlen = %d\n", len);
	for (x = 0; x < len; x++) {

		jobject obj = (*env)->GetObjectArrayElement(env, returnArray, x);
		printf("\t\tfilepath = %s\n", (*env)->GetStringUTFChars(env, obj, 0));
		filler(buf, (*env)->GetStringUTFChars(env, obj, 0), NULL, 0);

	}
//
	release_env(env);
	return 0;
}

static int peter_getattr(const char *path, struct stat *stbuf) {
	JNIEnv *env = get_env();
	printf("peter_getattr = %s\n", path);
	int res = 0;
	printf("s1\n");
	memset(stbuf, 0, sizeof(struct stat));
	printf("s2\n");
	printf("s3\n");

	printf("s4\n");
	jstring pathPara = (*env)->NewStringUTF(env, path);
	printf("s5\n");
	long blockNo = (*env)->CallStaticLongMethod(env, cls, getBlockNo, pathPara);
	printf("blockNo=%ld\n", blockNo);
	if (blockNo == -1) {
		release_env(env);
		return -ENOENT;
	}

	pathPara = (*env)->NewStringUTF(env, path);
	bool isFileReturn = (*env)->CallStaticBooleanMethod(env, cls, isFile, pathPara);
	printf("path=%s, %x\n", path, isFileReturn);
	if (isFileReturn) {
		jlong blockNoL = blockNo;
		int length = (*env)->CallStaticLongMethod(env, cls, getFileLength, blockNoL);
		stbuf->st_mode = S_IFREG | 0444;
		stbuf->st_nlink = 1;
		stbuf->st_size = length;

		printf("length=%d\n", length);
	} else {
		stbuf->st_mode = S_IFDIR | 0755;
		stbuf->st_nlink = 1;
	}
	release_env(env);
	return res;
}

static int peter_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
	JNIEnv *env = get_env();
	printf("peter_read = %s\n", path);

	jstring pathPara = (*env)->NewStringUTF(env, path);
	long blockNo = (*env)->CallStaticLongMethod(env, cls, getBlockNo, pathPara);
	if (blockNo == -1) {
		release_env(env);
		return -ENOENT;
	}

//	jlong blockNoL = blockNo;
	jstring content = (jstring)(*env)->CallStaticObjectMethod(env, cls, readFileContent, blockNo);
	const char *jcstr = (*env)->GetStringUTFChars(env, content, 0);
	printf("jcstr=%s\n", jcstr);
	//len = strlen(jcstr);
	int length = (*env)->GetStringLength(env, content);
	if (offset < length) {
		if (offset + size > length) {
			size = length - offset;
		}
		memcpy(buf, jcstr + offset, size);
	} else {
		size = 0;
	}
	release_env(env);
	return size;
}

static int peter_create(const char *path, mode_t mode, struct fuse_file_info *fi) {
	printf("peter_create = %s\n", path);
	return 1;
}

static int peter_fgetattr(const char *path, struct stat *buf, struct fuse_file_info *fi) {
	printf("peter_fgetattr\n");
	return 0;
}
int peter_rename(const char *oldpath, const char *newpath) {
	printf("peter_rename\n");
	return 0;
}
int peter_unlink(const char *path) {
	printf("peter_unlink\n");
	return 0;
}
int peter_rmdir(const char *path) {
	printf("peter_rmdir\n");
	return 0;
}
int peter_symlink(const char *linkname, const char *path) {
	printf("peter_symlink\n");
	return 0;
}
int peter_link(const char *oldpath, const char *newpath) {
	printf("peter_link\n");
	return 0;
}
int peter_release(const char *path, struct fuse_file_info *fi) {
	printf("peter_release\n");
	return 0;
}
int peter_write(const char *path, const char *buf, size_t size, off_t off, struct fuse_file_info *fi) {
	printf("peter_write\n");
	return 0;
}
int peter_fsync(const char *path, int datasync, struct fuse_file_info *fi) {
	printf("peter_fsync\n");
	return 0;
}
int peter_flush(const char *path, struct fuse_file_info *fi) {
	printf("peter_flush\n");
	return 0;
}
int peter_statfs(const char *path, struct statvfs *buf) {
	printf("peter_statfs, path=%s\n", path);
	buf->f_namemax = 255;
	buf->f_bsize = 1024;
	buf->f_frsize = buf->f_bsize;
	buf->f_blocks = buf->f_bfree = buf->f_bavail = 1000ULL * 1024 * 1024 * 1024 / buf->f_frsize;
	buf->f_files = buf->f_ffree = 1000000000;
	printf("peter_statfs end\n");
	return 0;
}
int peter_opendir(const char *path, struct fuse_file_info *fi) {
	printf("peter_opendir\n");
	return 0;
}
int peter_fsyncdir(const char *path, int datasync, struct fuse_file_info *fi) {
	printf("peter_fsyncdir\n");
	return 0;
}
int peter_releasedir(const char *path, struct fuse_file_info *fi) {
	printf("peter_releasedir\n");
	return 0;
}
int peter_lock(const char *path, struct fuse_file_info *fi, int cmd, struct flock *lock) {
	printf("peter_lock\n");
	return 0;
}
int peter_chmod(const char *path, mode_t mode) {
	printf("peter_chmod\n");
	return 0;
}
int peter_chown(const char *path, uid_t uid, gid_t gid) {
	printf("peter_chown\n");
	return 0;
}
int peter_truncate(const char *path, off_t size) {
	printf("peter_truncate\n");
	return 0;
}
int peter_ftruncate(const char *path, off_t size, struct fuse_file_info *fi) {
	printf("peter_ftruncate\n");
	return 0;
}
int peter_utimens(const char *path, const struct timespec tv[2]) {
	printf("peter_utimens\n");
	return 0;
}
int peter_access(const char *path, int mask) {
	printf("peter_access\n");
	return 0;
}
int peter_readlink(const char *path, char *buf, size_t len) {
	printf("peter_readlink\n");
	return 0;
}
int peter_mknod(const char *path, mode_t mode, dev_t rdev) {
	printf("peter_mknod\n");
	return 0;
}
int peter_mkdir(const char *path, mode_t mode) {
	printf("peter_mkdir\n");
	return 0;
}

#ifdef __APPLE__
int peter_setxattr(const char *path, const char *name, const char *value, size_t size, int flags, uint32_t position) {
	printf("peter_setxattr\n");
	return 0;
}
#else
int peter_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) {
	printf("peter_setxattr\n");
	return 0;
}
#endif
#ifdef __APPLE__
int peter_getxattr(const char *path, const char *name, char *value, size_t size, uint32_t position) {
	printf("peter_getxattr\n");
	return 0;
}
#else
int peter_getxattr(const char *path, const char *name, char *value, size_t size) {
	printf("peter_getxattr\n");
	return 0;
}
#endif
int peter_listxattr(const char *path, char *list, size_t size) {
	printf("peter_listxattr\n");
	return 0;
}
int peter_removexattr(const char *path, const char *name) {
	printf("peter_removexattr\n");
	return 0;
}
int peter_bmap(const char *path, size_t blocksize, uint64_t *idx) {
	printf("peter_bmap\n");
	return 0;
}
void * peter_init(struct fuse_conn_info *conn) {
	printf("peter_init\n");
	return NULL;
}
void *peter_destroy(void *fs) {
	printf("peter_destroy\n");
	return NULL;
}

static struct fuse_operations peter_oper = { .open = peter_open, .readdir = peter_readdir, .getattr = peter_getattr, .read = peter_read, .fgetattr = peter_fgetattr, .rename =
		peter_rename, .unlink = peter_unlink, .rmdir = peter_rmdir, .symlink = peter_symlink, .link = peter_link, .release = peter_release, .write = peter_write, .fsync =
		peter_fsync, .flush = peter_flush, .statfs = peter_statfs, .opendir = peter_opendir, .fsyncdir = peter_fsyncdir, .releasedir = peter_releasedir, .lock = peter_lock,
		.chmod = peter_chmod, .chown = peter_chown, .truncate = peter_truncate, .ftruncate = peter_ftruncate, .utimens = peter_utimens, .access = peter_access, .readlink =
				peter_readlink, .mknod = peter_mknod, .mkdir = peter_mkdir, .setxattr = peter_setxattr, .getxattr = peter_getxattr, .listxattr = peter_listxattr, .removexattr =
				peter_removexattr, .bmap = peter_bmap, .init = peter_init, .destroy = peter_destroy, .create = peter_create, };

int main(int argc, char *argv[]) {
	//env = create_vm(&jvm);

	JNIEnv * env;
	if ((env = create_vm()) == NULL) {
		printf("create_vm error\n");
		exit(-1);
	}

	cls = (*env)->FindClass(env, "com/pfsbuilder/fuse/FuseStub");
	if (cls == 0) {
		printf("load class error\n");
		exit(-1);
	}

	readdir = (*env)->GetStaticMethodID(env, cls, "readdir", "(Ljava/lang/String;)[Ljava/lang/String;");
	if (readdir == 0) {
		printf("load readdir error\n");
		exit(-1);
	}

	isFile = (*env)->GetStaticMethodID(env, cls, "isFile", "(Ljava/lang/String;)Z");
	if (isFile == 0) {
		printf("load isFile error\n");
		exit(-1);
	}

	getBlockNo = (*env)->GetStaticMethodID(env, cls, "getBlockNo", "(Ljava/lang/String;)J");
	if (getBlockNo == 0) {
		printf("load getBlockNo error\n");
		exit(-1);
	}

	readFileContent = (*env)->GetStaticMethodID(env, cls, "readFileContent", "(J)Ljava/lang/String;");
	if (readFileContent == 0) {
		printf("load readFileContent error\n");
		exit(-1);
	}

	getFileLength = (*env)->GetStaticMethodID(env, cls, "getFileLength", "(J)J");
	if (getFileLength == 0) {
		printf("load getFileLength error\n");
		exit(-1);
	}

	printf("starting fuse\n");

	fuse_main(argc, argv, &peter_oper, NULL);
	free_JVM(env);
	return 0;
}
